package dominion.player;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dominion.Action;
import dominion.Card;
import dominion.CardTypes;
import dominion.Dominion;
import dominion.Player;
import dominion.Tresure;
import dominion.Value;
import dominion.Victory;

public abstract class AbstractPlayer implements Player {

	static final Logger LOG = LoggerFactory.getLogger(AbstractPlayer.class);
	protected static final Comparator<Card> DEFAULT_CARD_SORTER = new Comparator<Card>() {
		@Override
		public int compare(Card l, Card r) {
			return l.getType().compareTo(r.getType());
		}
	};

	private Map<CardTypes, Integer> cardStat = new EnumMap<CardTypes, Integer>(
			CardTypes.class);

	private int turn = 0;
	private String name;
	private List<Card> hand = new ArrayList<Card>();
	private List<Card> playActions = new ArrayList<Card>();
	private int buycount = 1;
	private int temporaryCoin = 0;
	private LinkedList<Card> deck = new LinkedList<Card>();
	private List<Card> grave = new ArrayList<Card>();
	private int attacked;
	private int moat;

	@Override
	public void addAttackedCount() {
		this.attacked++;
	}

	@Override
	public int attcked() {
		return this.attacked;
	}

	@Override
	public void addProtectedCount() {
		moat++;
	}

	@Override
	public int protectedCount() {
		return moat;
	}

	public AbstractPlayer(String name, Dominion dominion) {
		this.name = name;
		for (int i = 0; i < 7; i++) {
			this.gain(Tresure.COPPER);
		}
		for (int i = 0; i < 3; i++) {
			this.gain(Victory.ESTATE);
		}
		draw5cards(dominion);
	}

	@Override
	public String name() {
		return this.name;
	}

	@Override
	public List<Card> hand() {
		return this.hand;
	}

	@Override
	public List<Card> deck() {
		return this.deck;
	}

	@Override
	public List<Card> grave() {
		return this.grave;
	}

	protected int turn() {
		return this.turn;
	}

	@Override
	public int count(CardTypes type) {
		Integer result = this.cardStat.get(type);
		if (result == null) {
			result = 0;
		}
		return result;
	}

	@Override
	public double coinRatio() {
		List<Card> all = new ArrayList<Card>();
		all.addAll(hand());
		all.addAll(deck());
		all.addAll(grave());
		int size = all.size();
		double d = 0d;
		for (Card c : all) {
			if (CardTypes.isTresure(c)) {
				Tresure t = (Tresure) c;
				d += t.value();
			}
		}
		double result = d / size;
		LOG.info("{} coin ratio {}", this.name, result);
		return result;
	}

	@Override
	public void trash(Card card) {
		LOG.info("{} trash {}", this.name, card);
		Integer count = this.cardStat.remove(card.getType());
		if (count != null) {
			count -= 1;
		} else {
			count = Integer.valueOf(0);
		}
		if (0 < count) {
			this.cardStat.put(card.getType(), count);
		}
	}

	@Override
	public void gain(Card card) {
		LOG.info("{} gain {}", this.name, card);
		addCardStat(card);
		this.grave.add(card);
	}

	protected void addCardStat(Card card) {
		Integer count = this.cardStat.get(card.getType());
		if (count == null) {
			count = 1;
		} else {
			count += 1;
		}
		this.cardStat.put(card.getType(), count);
	}

	@Override
	public void discard(Card card) {
		LOG.info("{} discards {}", this.name, card);
		this.grave.add(card);
	};

	@Override
	public int turnCount() {
		return this.turn;
	}

	@Override
	public void turn(Dominion dominion) {
		LOG.info("{}'s {} turn start", this.name, ++turn);
		LOG.info("hand {}", this.hand);
		// action phase
		act(dominion);
		// buy phase
		int value = tresure() + this.temporaryCoin;
		LOG.info("buy {}", this.buycount);
		for (int i = 0; i < this.buycount && 0 < value; i++) {
			LOG.info("current coin value {}", value);
			value = buy(dominion, value);
		}
		// cleanup phase
		cleanUp(dominion);
		LOG.info("{} turn end {}", turn, this);
	}

	@Override
	public void draw(Dominion dominion) {
		Card c = drawFromDeck(dominion);
		LOG.info("{} draw {}", this.name, c);
		if (c != null) {
			this.hand.add(c);
			Collections.sort(this.hand, sorter());
		}
	}

	protected Comparator<Card> sorter() {
		return DEFAULT_CARD_SORTER;
	}

	protected Card drawFromDeck(Dominion dominion) {
		if (this.deck.size() < 1) {
			this.deck.addAll(this.grave);
			this.grave.clear();
			Collections.shuffle(this.deck, dominion.random());
		}
		if (0 < this.deck.size()) {
			return this.deck.removeFirst();
		}
		return null;
	}

	@Override
	public Card reveal(Dominion dominion) {
		Card c = drawFromDeck(dominion);
		LOG.info("{} reveal {}", this.name, c);
		return c;
	}

	@Override
	public void putOnDeck(Card card, boolean gain) {
		LOG.info("{} put {} on deck", this.name, card);
		if (gain) {
			LOG.info("{} gain {}", this.name, card);
			addCardStat(card);
		}
		this.deck.addFirst(card);
	}

	@Override
	public void act(Dominion dominion) {
		for (Card c : new ArrayList<Card>(hand())) {
			if (c instanceof Action) {
				this.hand.remove(c);
				Action a = (Action) c;
				a.execute(dominion);
				this.playActions.add(c);
				break;
			}
		}
	}

	@Override
	public void cleanUp(Dominion dominion) {
		this.grave.addAll(this.playActions);
		this.playActions.clear();
		this.grave.addAll(this.hand);
		this.hand.clear();
		draw5cards(dominion);
		this.buycount = 1;
		this.temporaryCoin = 0;
	}

	protected void draw5cards(Dominion dominion) {
		for (int i = 0; i < 5; i++) {
			draw(dominion);
		}
	}

	@Override
	public int tresure() {
		return this.countValue(this.hand, CardTypes.Tresures);
	}

	protected int countValue(Collection<Card> list, Set<CardTypes> validTypes) {
		int result = 0;
		for (Card c : list) {
			if (c instanceof Value && validTypes.contains(c.getType())) {
				Value t = (Value) c;
				result += t.value();
			}
		}
		return result;
	}

	private int countVictory(CardTypes type, int value) {
		Integer i = this.cardStat.get(type);
		if (i == null) {
			i = 0;
		}
		return i * value;
	}

	@Override
	public int victory() {
		int value = countVictory(CardTypes.Estate, Victory.ESTATE.value());
		value += countVictory(CardTypes.Duchy, Victory.DUCHY.value());
		value += countVictory(CardTypes.Province, Victory.PROVINCE.value());
		value += countVictory(CardTypes.Curse, Victory.CURSE.value());
		return value;
	}

	@Override
	public void addBuy() {
		this.buycount++;
	}

	@Override
	public void addTemporaryCoin(int value) {
		this.temporaryCoin += value;
	}

	@Override
	public String toString() {
		List<CardTypes> list = new ArrayList<CardTypes>(this.cardStat.keySet());
		Collections.sort(list);
		StringBuilder stb = new StringBuilder();
		for (CardTypes ct : list) {
			stb.append("{");
			stb.append(ct.name() + " * " + this.cardStat.get(ct));
			stb.append("}");
		}
		return "[" + stb.toString() + "]";
	}
}
